package com.vhall.component.service.pay.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.alibaba.fastjson.JSON;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.model.UnifiedOrderModel;
import com.vhall.component.entity.pay.entity.PaymentOrderEntity;
import com.vhall.component.framework.common.exception.BusinessException;
import com.vhall.component.plugin.common.exception.BizErrorCode;
import com.vhall.component.service.pay.SettingService;
import com.vhall.component.service.pay.constant.Channel;
import com.vhall.component.service.pay.constant.ServiceCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;

/**
 * @author xuyongzheng
 * @since 2020/10/22
 **/
@Slf4j
@Component
public class WxPayService {

    private static final String APP_ID = "appid";
    private static final String MCH_ID = "mchid";
    private static final String PAY_WX_NOTIFY = "/v4/pay/wx-notify";
    private static final String RETURN_CODE = "return_code";
    private static final String RESULT_CODE = "result_code";

    @Value("${vhall.pay.domain}")
    private String domain;

    @Autowired
    private SettingService settingService;

    public String qrPayOrder(PaymentOrderEntity entity) {

        Map<String, String> setting = settingService.mapBy(entity.getAppId(), Channel.WEIXIN, ServiceCode.QR_PAY);

        if (null == setting || setting.size() == 0) {
            throw new BusinessException(BizErrorCode.COMP_PAY_PARAM_UNSET);
        }
        try {
            long fee = new BigDecimal("100").multiply(entity.getTotalFee()).longValue();
            Map<String, String> params = UnifiedOrderModel
                    .builder()
                    .appid(setting.get(APP_ID))
                    .mch_id(setting.get(MCH_ID))
                    .nonce_str(WxPayKit.generateStr())
                    .body(entity.getTitle())
                    .time_start(LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN))
                    .time_expire(LocalDateTimeUtil.format(LocalDateTime.now().plusDays(1), DatePattern.PURE_DATETIME_PATTERN))
                    //.spbill_create_ip("123.123.123.123") // 终端ip
                    .notify_url(domain + PAY_WX_NOTIFY)
                    .trade_type("NATIVE")
                    .out_trade_no(entity.getBizOrderNo())
                    .total_fee(String.valueOf(fee)) // 单位是分
                    .product_id(entity.getBizOrderNo())//NATIVE 此处必传
                    .build()
                    .createSign(setting.get("key"), SignType.MD5);
            /**
             * <xml><return_code><![CDATA[SUCCESS]]></return_code>
             * <return_msg><![CDATA[OK]]></return_msg>
             * <appid><![CDATA[wxb3a71690c5b5c59e]]></appid>
             * <mch_id><![CDATA[1500974681]]></mch_id>
             * <nonce_str><![CDATA[zvkC6o42ELGDAcpR]]></nonce_str>
             * <sign><![CDATA[AB571CCD05BE34A969B525523AFB74D3]]></sign>
             * <result_code><![CDATA[SUCCESS]]></result_code>
             * <prepay_id><![CDATA[wx1816424966680709c11bcd2736ece60000]]></prepay_id>
             * <trade_type><![CDATA[NATIVE]]></trade_type>
             * <code_url><![CDATA[weixin://wxpay/bizpayurl?pr=5FEatNi00]]></code_url>
             * </xml>
             */
            String xmlResult = WxPayApi.pushOrder(false, params);
            Map<String, String> resultMap = WxPayKit.xmlToMap(xmlResult);

            String returnCode = resultMap.get(RETURN_CODE);

            if (!WxPayKit.codeIsOk(returnCode)) {
                log.error("微信NATIVE预下单失败，response:[{}]", JSON.toJSONString(resultMap));
                throw new BusinessException(BizErrorCode.COMP_PAY_WX_ORDER_FAIL);
            }
            String resultCode = resultMap.get(RESULT_CODE);

            if (!WxPayKit.codeIsOk(resultCode)) {
                log.error("微信NATIVE预下单失败，response:[{}]", JSON.toJSONString(resultMap));
                throw new BusinessException(BizErrorCode.COMP_PAY_WX_ORDER_FAIL);
            }
            // 以下字段在return_code 和result_code都为SUCCESS的时候有返回
            String prepayId = resultMap.get("prepay_id");
            // 二次签名，构建公众号唤起支付的参数,这里的签名方式要与上面统一下单请求签名方式保持一致
            Map<String, String> packageParams = WxPayKit.prepayIdCreateSign(prepayId,
                    setting.get(APP_ID), setting.get("key"), SignType.MD5);
            // 将二次签名构建的数据返回给前端并唤起公众号支付
            String jsonStr = JSON.toJSONString(packageParams);
            log.info("微信NATIVE预下单成功，[{}]", jsonStr);
            return resultMap.get("code_url");
        } catch (BusinessException buex) {
            log.error("微信支付QR-PAY下单异常:", buex);
            throw new BusinessException(buex.getCode(), buex.getMsg(), buex.getKey());
        } catch (Exception ex) {
            log.error("微信支付QR-PAY下单异常:", ex);
            throw new BusinessException(BizErrorCode.COMP_PAY_ORDER_FAIL);
        }
    }

    /**
     * H5端支付
     *
     * @param entity
     * @return
     */
    public Object wapPayOrder(PaymentOrderEntity entity) {
        Map<String, String> setting = settingService.mapBy(entity.getAppId(), Channel.WEIXIN, ServiceCode.H5_PAY);
        if (null == setting || setting.size() == 0) {
            throw new BusinessException(BizErrorCode.COMP_PAY_PARAM_UNSET);
        }
        if (StringUtils.isBlank(entity.getClientIp())) {
            //微信h5支付 client_ip 字段必填
            throw new BusinessException(BizErrorCode.BIZ_INVALID_PARAM);
        }
        try {
            long fee = new BigDecimal("100").multiply(entity.getTotalFee()).longValue();
            Map<String, String> params = UnifiedOrderModel
                    .builder()
                    .appid(setting.get(APP_ID))
                    .mch_id(setting.get(MCH_ID))
                    .nonce_str(WxPayKit.generateStr())
                    .body(entity.getTitle())
                    .time_start(LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN))
                    .time_expire(LocalDateTimeUtil.format(LocalDateTime.now().plusDays(1), DatePattern.PURE_DATETIME_PATTERN))
                    .spbill_create_ip(entity.getClientIp())
                    .notify_url(domain + PAY_WX_NOTIFY)
                    .trade_type("MWEB")
                    .out_trade_no(entity.getBizOrderNo())
                    .total_fee(String.valueOf(fee)) // 单位是分
                    .product_id(entity.getBizOrderNo())//NATIVE 此处必传
                    .build()
                    .createSign(setting.get("key"), SignType.MD5);
            String xmlResult = WxPayApi.pushOrder(false, params);
            Map<String, String> resultMap = WxPayKit.xmlToMap(xmlResult);

            String returnCode = resultMap.get(RETURN_CODE);

            if (!WxPayKit.codeIsOk(returnCode)) {
                log.error("微信NATIVE预下单失败，response:[{}]", JSON.toJSONString(resultMap));
                throw new BusinessException(BizErrorCode.COMP_PAY_WX_ORDER_FAIL);
            }
            String resultCode = resultMap.get(RESULT_CODE);

            if (!WxPayKit.codeIsOk(resultCode)) {
                log.error("微信NATIVE预下单失败，response:[{}]", JSON.toJSONString(resultMap));
                throw new BusinessException(BizErrorCode.COMP_PAY_WX_ORDER_FAIL);
            }
            log.info("微信MWEB预下单成功，resultMap:[{}]", JSON.toJSONString(resultMap));
            return resultMap;

        } catch (BusinessException buex) {
            log.error("微信支付H5-PAY下单异常:", buex);
            throw new BusinessException(buex.getCode(), buex.getMsg(), buex.getKey());
        } catch (Exception ex) {
            log.error("微信支付H5-PAY下单异常:", ex);
            throw new BusinessException(BizErrorCode.COMP_PAY_ORDER_FAIL);
        }
    }

    public Object jsApiOrder(PaymentOrderEntity entity) {
        Map<String, String> setting = settingService.mapBy(entity.getAppId(), Channel.WEIXIN, ServiceCode.JSAPI);
        if (null == setting || setting.size() == 0) {
            throw new BusinessException(BizErrorCode.COMP_PAY_PARAM_UNSET);
        }
        if (StringUtils.isEmpty(entity.getOpenId())) {
            //JSAPI open_id 必须字段
            throw new BusinessException(BizErrorCode.BIZ_INVALID_PARAM);
        }
        if (StringUtils.isEmpty(entity.getClientIp())) {
            //微信h5支付 client_ip 字段必填
            throw new BusinessException(BizErrorCode.BIZ_INVALID_PARAM);
        }
        try {
            long fee = new BigDecimal("100").multiply(entity.getTotalFee()).longValue();
            Map<String, String> params = UnifiedOrderModel
                    .builder()
                    .appid(setting.get(APP_ID))
                    .mch_id(setting.get(MCH_ID))
                    .nonce_str(WxPayKit.generateStr())
                    .body(entity.getTitle())
                    .time_start(LocalDateTimeUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN))
                    .time_expire(LocalDateTimeUtil.format(LocalDateTime.now().plusDays(1), DatePattern.PURE_DATETIME_PATTERN))
                    .spbill_create_ip(entity.getClientIp()) // 终端ip
                    .notify_url(domain + PAY_WX_NOTIFY)
                    .trade_type("JSAPI")
                    .openid(entity.getOpenId())
                    .out_trade_no(entity.getBizOrderNo())
                    .total_fee(String.valueOf(fee)) // 单位是分
                    .product_id(entity.getBizOrderNo())//NATIVE 此处必传
                    .build()
                    .createSign(setting.get("key"), SignType.MD5);
            String xmlResult = WxPayApi.pushOrder(false, params);
            Map<String, String> resultMap = WxPayKit.xmlToMap(xmlResult);

            String returnCode = resultMap.get(RETURN_CODE);
            String resultCode = resultMap.get(RESULT_CODE);
            String errCode = resultMap.get("err_code");

            if (!WxPayKit.codeIsOk(returnCode) || !WxPayKit.codeIsOk(resultCode)) {
                log.error("微信JSAPI预下单失败，response:[{}]", JSON.toJSONString(resultMap));
                if ("PARAM_ERROR".equals(errCode)) {
                    // 这里可能是open_id跟app_id不匹配，所以需要前端重新调起授权获取新的open_id和app_id
                    throw new BusinessException(BizErrorCode.COMP_PAY_WX_PARAM_FAIL);
                }
                throw new BusinessException(BizErrorCode.COMP_PAY_WX_ORDER_FAIL);
            }
            log.info("微信JSAPI预下单成功，resultMap:[{}]", JSON.toJSONString(resultMap));

            // 以下字段在return_code 和result_code都为SUCCESS的时候有返回
            String prepayId = resultMap.get("prepay_id");
            // 二次签名，构建公众号唤起支付的参数,这里的签名方式要与上面统一下单请求签名方式保持一致
            Map<String, String> packageParams = WxPayKit.prepayIdCreateSign(prepayId,
                    setting.get(APP_ID), setting.get("key"), SignType.MD5);
            // 将二次签名构建的数据返回给前端并唤起公众号支付
            String jsonStr = JSON.toJSONString(packageParams);
            log.info("微信JSAPI预下单成功，packageParam:[{}]", jsonStr);
            return packageParams;

        } catch (BusinessException buex) {
            log.error("微信支付JSAPI下单异常:", buex);
            throw new BusinessException(buex.getCode(), buex.getMsg(), buex.getKey());
        } catch (Exception ex) {
            log.error("微信支付JSAPI下单异常:", ex);
            throw new BusinessException(BizErrorCode.COMP_PAY_ORDER_FAIL);
        }
    }
}
